<?php
/**
 * Реализация IOC контейнера (аналог Spring IOC)
 * изменение 1
 * @author Захаров П.Ю.
 * @category WhiteCMS
 * @package Wh_Ioc
 * изменение 3
 */
class Wh_Ioc_Context
{
	/**
	 * Флаг, показывающий что объект в данный момент находится в стадии инициализации
	 *
	 * @var string
	 */
	const IN_PROCESS = 'inprocess';

	/**
	 * Загруженный XML файл с контекстом
	 *
	 * @var SimpleXMLElement
	 */
	private $xmlContext;

	/**
	 * Массив инициализированных объектов, в момент начала инициализации объекта его устанавливается в значение 'inprocess'
	 *
	 * @var array
	 */
	private static $objects = array();

	/**
	 *
	 * @var SimpleXMLElement
	 */
	private $attributes;

	/**
	 *
	 * @param string $context Имя XML файла с контекстом
	 *
	 */
	public function __construct($context) {
		$this->xmlContext = simplexml_load_file($context);
		//Zend_Debug::dump($this->xmlContext);
		foreach($this->xmlContext as $object) //инициируем каждый объект согласно его конфигурации
		{
			$id = (string)$object['id'];
			$obj = $this->createObject($object);
			self::$objects[$id] = $obj;
		}
	}

	/**
	 * Инициализация конкретного объекта
	 *
	 * @param SimpleXMLElement $object
	 * @return mixed Инициализированный объект
	 */
	private function createObject(SimpleXMLElement $object)
	{
		$class = (string)$object['class'];
		$id = (string)$object['id'];

		self::$objects[$id]  = Wh_Ioc_Context::IN_PROCESS;

		$args = $this->getConstructorArgs($object);

		//Zend_Debug::dump($args);
		$objClass = new ReflectionClass($class);
		$obj = $objClass->newInstanceArgs($args);
		$obj = $this->setProperties($object, $obj);
		return $obj;
	}

	/**
	 * Возвращает массив с аргументами конструктора
	 * @param SimpleXMLElement $object
	 * @return array
	 */
	private function getConstructorArgs(SimpleXMLElement $object)
	{
		$result = array();
		foreach($object->constructorArg as $node)
		{
			$result[] = $this->getElementValue($node);
		}
		return $result;
	}
	private function setProperties(SimpleXMLElement $object, $instance)
	{
		$result = array();
		foreach($object->property as $node)
		{
			$class = (string)$object['class'];
			$name = (string)$node['name'];
			$value = $this->getElementValue($node);
			$obj = $this->setProperty($class, $instance, $name, $value);
		}
		return $obj;
	}

	private function getElementValue(SimpleXMLElement $object)
	{
		$type = (string)$object['type'];
		switch($type)
		{
			case 'string' : $result = $this->getStringValue($object);
			break;
			case 'int'    : $result = $this->getIntValue($object);
			break;
			case 'ref'    : $result = $this->getRefValue((string)$object->value);
			break;
			case 'list'   : $result = $this->getListValue($object);
			break;
			case 'map'    : $result = $this->getMapValue($object);
			break;
				
		}
		return $result;
	}
	private function getStringValue($object)
	{
		return (string)$object->value;
	}
	private function getIntValue($object)
	{
		return (int)$object->value;
	}
	private function getListValue($object)
	{
		$result = array();
		foreach($object->value as $value)
		{
			$result[] = (string)$value;
		}
		return $result;
	}
	private function getMapValue($object)
	{
		$result = array();
		foreach($object->entry as $entry)
		{
			$key = (string)$entry->key;
			$result[$key] = (string)$entry->value;
		}
		return $result;
	}
	private function getRefValue($id)
	{
		$object = $this->getObjectById($id);
		return $this->createObject($object[0]);
	}
	private function getObjectById($id)
	{
		$path = 'object[@id="'.$id.'"]';
		return $this->xmlContext->xpath($path);
	}
	private function setProperty($class, $object, $property, $value)
	{
		$method = new ReflectionMethod($class, $this->getSetterName($property));
		$method->invoke($object, $value);
		return $object;
	}

	private function getSetterName($propName)
	{
		return 'set'.ucfirst($propName);
	}
	public function get($id)
	{
		return self::$objects[$id];
	}
}